package cn.yunbamboos.mybatis.metadata;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.yunbamboos.exception.AppException;
import cn.yunbamboos.mybatis.annotation.DateRange;
import cn.yunbamboos.mybatis.annotation.ForEach;
import cn.yunbamboos.mybatis.annotation.OrderBy;
import cn.yunbamboos.mybatis.annotation.TableField;
import cn.yunbamboos.mybatis.annotation.TableName;
import cn.yunbamboos.mybatis.annotation.TablePrimaryKey;
import cn.yunbamboos.mybatis.metadata.where.TableWhere;
import cn.yunbamboos.mybatis.metadata.where.item.TableWhereDateRange;
import cn.yunbamboos.mybatis.metadata.where.item.TableWhereForEach;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

public class TableInfoHelper {

    private static final Logger log = LoggerFactory.getLogger(TableInfoHelper.class);
    /**
     * 储存反射类表信息
     */
    private static final Map<Class<?>, TableInfo> TABLE_INFO_CACHE = new ConcurrentHashMap<>();
    /**
     * 储存表名对应的反射类表信息
     */
    private static final Map<String, TableInfo> TABLE_NAME_INFO_CACHE = new ConcurrentHashMap<>();

    public static Optional<TableInfo> getTableInfo(Class<?> clazz){
        return Optional.ofNullable(TABLE_INFO_CACHE.get(clazz));
    }

    public static Optional<TableInfo> getTableInfo(String tableName){
        return Optional.ofNullable(TABLE_NAME_INFO_CACHE.get(tableName));
    }


    /**
     * <p>
     * 实体类反射获取表信息【初始化】
     * </p>
     *
     * @param clazz 反射实体类
     * @return 数据库表反射信息
     */
    public static synchronized TableInfo initTableInfo(Configuration configuration, Class<?> clazz, Class<?> mapper) {
        TableInfo targetTableInfo = TABLE_INFO_CACHE.get(clazz);
        if (targetTableInfo != null) {
            return targetTableInfo;
        }
        TableInfo tableInfo = new TableInfo(configuration);
        /* 设置Mapper类型 */
        tableInfo.setMapperType(mapper);
        /* 设置实体类型 */
        tableInfo.setEntityType(clazz);
        /* 初始化表名相关 */
        initTableName(clazz, tableInfo);
        /* 初始化字段相关 */
        initTableFields(clazz, tableInfo);

        /* 自动构建 resultMap */
        tableInfo.initResultMapIfNeed();

        TABLE_INFO_CACHE.put(clazz, tableInfo);
        TABLE_NAME_INFO_CACHE.put(tableInfo.getTableName(), tableInfo);

        return tableInfo;
    }

    /**
     * <p>
     * 初始化 表数据库类型,表名,resultMap
     * </p>
     *
     * @param clazz     实体类
     * @param tableInfo 数据库表反射信息
     */
    private static void initTableName(Class<?> clazz, TableInfo tableInfo) {
        TableName table = clazz.getAnnotation(TableName.class);
        if (ObjUtil.isNotNull(table)) {
            // 设置注解数据库表名称
            tableInfo.setTableName(table.value());
        } else {
            throw AppException.c(clazz.getSimpleName() + " not fount @TableName");
        }
    }

    /**
     * <p>
     * 初始化 表主键,表字段
     * </p>
     *
     * @param clazz     实体类
     * @param tableInfo 数据库表反射信息
     */
    private static void initTableFields(Class<?> clazz, TableInfo tableInfo) {
        // 读取所有属性
        Field[] fields = ReflectUtil.getFields(clazz);
        // 标记是否读取到主键
        boolean isReadPK = false;
        // 主键列表
        List<TablePrimaryKeyInfo> primaryKeyList = new ArrayList<>();
        // 数据库列列表
        List<TableFieldInfo> fieldList = new ArrayList<>();
        // 排序列表
        List<TableOrderInfo> orderList = new ArrayList<>();
        // 列表查询
        List<TableWhere> whereList = new ArrayList<>();

        for (Field field : fields) {
            TablePrimaryKey tablePrimaryKey = field.getAnnotation(TablePrimaryKey.class);
            if(ObjUtil.isNotNull(tablePrimaryKey)){
                primaryKeyList.add(new TablePrimaryKeyInfo(field, tablePrimaryKey));
                isReadPK = true;
                continue;
            }
            TableField tableField = field.getAnnotation(TableField.class);
            if(ObjUtil.isNotNull(tableField)){
                fieldList.add(new TableFieldInfo(field, tableField));
                // 读取排序信息
                OrderBy order = field.getAnnotation(OrderBy.class);
                if(ObjUtil.isNotNull(order)){
                    orderList.add(new TableOrderInfo(field, tableField, order));
                }
            }
            ForEach forEach = field.getAnnotation(ForEach.class);
            if(ObjUtil.isNotNull(forEach)){
                whereList.add(new TableWhereForEach(tableInfo, field, forEach));
            }
            DateRange dateRange = field.getAnnotation(DateRange.class);
            if(ObjUtil.isNotNull(dateRange)){
                whereList.add(new TableWhereDateRange(tableInfo, field, dateRange));
            }
        }

        // 主键按照sort排序
        Collections.sort(primaryKeyList);

        tableInfo.setPrimaryKeyList(primaryKeyList);
        tableInfo.setFieldList(fieldList);
        tableInfo.setOrderList(orderList);
        tableInfo.setWhereList(whereList);

        /* 未发现主键注解，提示警告信息 */
        if (!isReadPK) {
            log.warn("Can not find table primary key in Class: {}", clazz.getName());
        }
    }

}
